<template>
  <el-container>
    <el-main class="main">
      <el-form inline>
        <el-form-item label="模型ID">
          <el-input v-model="id" placeholder="请输入模型ID"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button bg icon="Search" size="mini" type="primary">搜索</el-button>
          <el-button bg icon="Upload" size="mini" type="warning" @click="statusDialog.trainDialog=true">训练</el-button>
        </el-form-item>
      </el-form>
      <el-table row-key="id" :data="modelList" current-row-key="lstmList" show-overflow-tooltip>
        <el-table-column prop="layer" label="层级" type="expand" width="80">
          <template #default="props">
            <el-table :data="props.row.lstmList" border>
              <el-table-column label="层级ID" prop="id"></el-table-column>
              <el-table-column label="学习速率" prop="learningRate"></el-table-column>
              <el-table-column label="隐藏神经元" prop="hiddenCell"></el-table-column>
              <el-table-column label="L2正则系数" prop="l2"></el-table-column>
              <el-table-column label="更新器">
                <template #default="scope">
                  <el-tag>{{ dict.find(item => item.code == scope.row.updater).value }}</el-tag>
                </template>
              </el-table-column>
              <el-table-column label="层级索引" prop="index">
              </el-table-column>
            </el-table>
          </template>
        </el-table-column>
        <el-table-column label="模型ID" prop="id" width="80px"></el-table-column>
        <el-table-column label="模型名称" prop="name" width="120px"></el-table-column>
        <el-table-column label="模型地址" prop="url"></el-table-column>
        <el-table-column label="特征步长" prop="featureStep"></el-table-column>
        <el-table-column label="标签步长" prop="labelStep"></el-table-column>
        <el-table-column label="训练轮数" prop="epoch"></el-table-column>
        <el-table-column label="训练批次大小" prop="batchSize" width="auto"></el-table-column>
        <el-table-column label="模型状态" prop="status" width="100px">
          <template #default="scope">
            <tag-dict :data="modelDict" :model-value="scope.row.status"/>
          </template>
        </el-table-column>
        <el-table-column prop="testInfo" label="测试集评估" width="100">
          <template #default="scope">
            <el-popover placement="bottom" width="480px" trigger="hover">
              <template #reference>
                <el-button style="margin-right: 16px" size="small" type="warning">评估结果</el-button>
              </template>
              <template #default>
                <el-scrollbar height="300">
                  <el-table :data="scope.row.testInfoList" table-layout="auto" border>
                    <el-table-column label="均方误差" prop="mse"></el-table-column>
                    <el-table-column label="均方误差根" prop="rmse"></el-table-column>
                    <el-table-column label="平均绝对误差" prop="mae"></el-table-column>
                    <el-table-column label="相对标准误差" prop="rse"></el-table-column>
                    <el-table-column label="决定系数" prop="r2"></el-table-column>
                    <el-table-column label="皮尔逊相关系数" prop="pc"></el-table-column>
                  </el-table>
                </el-scrollbar>
              </template>
            </el-popover>
          </template>
        </el-table-column>
        <el-table-column label="特征索引" width="100px">
          <template #default="scope">
            <span>{{ scope.row.featureStart }}—{{ scope.row.featureStop }}</span>
          </template>
        </el-table-column>
        <el-table-column label="标签索引" width="100px">
          <template #default="scope">
            <span>{{ scope.row.labelStart }}—{{ scope.row.labelStop }}</span>
          </template>
        </el-table-column>
        <el-table-column label="是否使用滑窗窗口" prop="timeWindow" width="80">
          <template #default="scope">
            <el-switch :model-value="scope.row.timeWindow"></el-switch>
          </template>
        </el-table-column>
        <el-table-column label="是否使用标准化" prop="standard" width="80">
          <template #default="scope">
            <el-switch :model-value="scope.row.standard"></el-switch>
          </template>
        </el-table-column>
        <el-table-column label="跳过行数" prop="skip" width="60"></el-table-column>
        <el-table-column fixed="right" label="操作" width="150">
          <template #default="scope">
            <el-button plain :disabled="scope.row.status!=3" size="mini" type="primary"
                       @click="handlePredict(scope.row)">
              预测
            </el-button>
            <el-button plain size="mini" type="danger" @click="handleRemove(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <el-dialog v-model="statusDialog.trainDialog" :close-on-click-modal="false" title="模型训练" width="800">
        <el-scrollbar max-height="500">
          <el-form ref="trainRef" :model="train" :rules="trainRules" class="form" inline label-width="140px">
            <el-row>
              <el-upload
                  :before-upload="beforeUpload"
                  :http-request="handleHttpRequest"
                  :limit="1"
                  :show-file-list="false"
                  accept=".csv"
                  action="#"
                  class="upload-demo"
                  drag
              >
                <el-icon class="el-icon--upload">
                  <upload-filled/>
                </el-icon>
                <div class="el-upload__text">
                  拖动文件到此处或者<em>点击</em>上传
                </div>
                <template #tip>
                  <div class="el-upload__tip">
                    只支持csv文件 <em style="color: #171b93">{{ file ? '文件已上传' : '请上传文件' }}</em>
                  </div>
                </template>
              </el-upload>
            </el-row>
            <el-form-item label="模型名称" prop="name">
              <el-input v-model="train.name" placeholder="请输入模型名称"></el-input>
            </el-form-item>
            <el-row>
              <el-form-item label="开始行数" prop="skip">
                <el-input v-model="train.skip"></el-input>
              </el-form-item>
              <el-form-item label="测试比例" prop="testRate">
                <el-input v-model="train.testRate" placeholder="请输入测试集比例"></el-input>
              </el-form-item>
            </el-row>
            <el-form-item label="特征开始列数" prop="featureStart">
              <el-input v-model="train.featureStart" placeholder="请输入特征开始列数"></el-input>
            </el-form-item>
            <el-form-item label="特征结束列数" prop="featureStop">
              <el-input v-model="train.featureStop" placeholder="请输入特征结束列数"></el-input>
            </el-form-item>
            <el-form-item label="标签开始列数" prop="featureStart">
              <el-input v-model="train.labelStart"></el-input>
            </el-form-item>
            <el-form-item label="标签结束列数" prop="featureStop">
              <el-input v-model="train.labelStop"></el-input>
            </el-form-item>
            <el-form-item label="特征步长" prop="featureStep">
              <el-input v-model="train.featureStep" placeholder="请输入特征步长"></el-input>
            </el-form-item>
            <el-form-item label="标签步长" prop="labelStep">
              <el-input v-model="train.labelStep"></el-input>
            </el-form-item>
            <el-form-item label="训练轮数" prop="epoch">
              <el-input v-model="train.epoch"></el-input>
            </el-form-item>
            <el-form-item label="训练批次" prop="batchSize">
              <el-input v-model="train.batchSize"></el-input>
            </el-form-item>
            <el-form-item label="是否使用滑动窗口" prop="timeWindow">
              <el-switch v-model="train.timeWindow" active-text="是" inactive-text="否">
              </el-switch>
            </el-form-item>
            <el-form-item label="是否标准化" prop="standard">
              <el-switch v-model="train.standard" active-text="是" inactive-text="否"></el-switch>
            </el-form-item>
            <el-row>
              <el-form-item class="lstm-param" label="LSTM模型参数" prop="lstm">
                <el-button v-show="train.lstm.length<1" size="mini" type="primary" @click="handleAddLayer()">新增
                </el-button>
                <VueDraggable v-model="train.lstm" animation="150" class="width-100" target="tbody">
                  <el-table v-show="train.lstm.length>0" :data="train.lstm">
                    <el-table-column label="学习速率" prop="learningRate" width="80px"></el-table-column>
                    <el-table-column label="隐藏单元元" prop="hiddenCell" width="100px"></el-table-column>
                    <el-table-column label="L2正则化" prop="l2" width="100px"></el-table-column>
                    <el-table-column label="优化器" prop="updater" width="80">
                      <template #default="scope">
                        {{ dict.filter(item => item.code == scope.row.updater).at(0).value }}
                      </template>
                    </el-table-column>
                    <el-table-column fixed="right" label="操作" width="auto">
                      <template #default="scope">
                        <el-button plain size="mini" type="primary" @click="handleAddLayer()">新增</el-button>
                        <el-button plain size="mini" type="warning" @click="handleAddLayer(scope.$index)">编辑
                        </el-button>
                        <el-button plain size="mini" type="danger" @click="handleDeleteLSTM(scope.$index)">删除
                        </el-button>
                      </template>
                    </el-table-column>
                  </el-table>
                </VueDraggable>
              </el-form-item>
            </el-row>
          </el-form>
          <el-dialog v-model="statusDialog.lstmDialog" :close-on-click-modal="false" title="LSTM层" width="640px">
            <el-form ref="lstmRef" :model="lstm" :rules="lstmRules" class="form" inline label-width="100px">
              <el-form-item label="学习速率" prop="learningRate">
                <el-input v-model="lstm.learningRate" placeholder="请输入学习速率"></el-input>
              </el-form-item>
              <el-form-item label="隐藏单元元" prop="hiddenCell">
                <el-input v-model="lstm.hiddenCell" placeholder="请输入隐藏神经元个数"></el-input>
              </el-form-item>
              <el-form-item label="L2正则化" prop="l2">
                <el-input v-model="lstm.l2" placeholder="请输入L2正则化"></el-input>
              </el-form-item>
              <el-form-item label="优化器" prop="updater">
                <el-select v-model="lstm.updater" placeholder="请选择优化器">
                  <el-option v-for="item in dict" :label="item.value" :value="item.code"></el-option>
                </el-select>
              </el-form-item>
            </el-form>
            <template #footer>
              <el-button v-show="statusDialog.lstmAdd==null" type="primary" @click="handleAdd">增加</el-button>
              <el-button v-show="statusDialog.lstmAdd!=null" type="primary" @click="handleEdit">修改</el-button>
            </template>
          </el-dialog>
        </el-scrollbar>
        <template #footer>
          <el-button type="primary" @click="trainModel">开始训练</el-button>
        </template>
      </el-dialog>
      <el-dialog align-center v-model="statusDialog.predictDialog" :close-on-click-modal="false" title="数据预测"
                 width="580px">
        <el-scrollbar max-height="600">
          <el-form class="predict-cell" :model="predictList.input">
            <el-form-item v-for="(item,index) in predictList.input" :label="'第'+(index+1)+'行'">
              <el-input v-model="predictList.input[index][li_i]"
                        v-for="(li,li_i) in predictList.input[index]"></el-input>
            </el-form-item>
            <el-form-item class="predict-item">
              <el-button @click="handlePredictPost" type="primary" plain size="mini">单步预测</el-button>
              <el-button @click="handleStep" plain type="warning" size="mini">多步预测</el-button>
            </el-form-item>
            <el-form-item label="结果">
              <div id="echarts" style="height:480px;width:480px"></div>
            </el-form-item>
          </el-form>
        </el-scrollbar>
      </el-dialog>
      <el-pagination
          v-if="page.total"
          v-model:current-page="page.current"
          v-model:page-size="page.size"
          :page-sizes="[10, 20, 30, 40,50]"
          layout="total,sizes, prev, pager, next"
          @size-change="initModel"
          @current-change="initModel"
          :total="page.total"
      />
    </el-main>
  </el-container>
</template>
<script lang="ts" setup>
import {onBeforeMount, ref} from "vue";
import {
  deleteModel,
  getUpdater,
  LstmListDataRecords,
  pageModels,
  predict,
  PredictData, predictStep,
  trainLstm,
  TrainModel,
  TrainModelLstm
} from "@/api/model.ts";
import {Dict, PageParams} from "@/api/common.ts";
import {VueDraggable} from "vue-draggable-plus";
import {FormInstance, FormRules} from "element-plus";
import {Common} from "@/utils/common.ts";
import TagDict from "@/components/dict/tag-dict.vue";
import * as echarts from 'echarts';
import "echarts-gl";

onBeforeMount(() => {
  init()
})

const init = () => {
  initUpdater()
}

const initUpdater = () => {
  getUpdater().then(res => {
    dict.value = res.data.data?.update
    modelDict.value = res.data.data?.status
  }).then(initModel)
}

const initModel = () => {
  pageModels({currentPage: page.value.current, pageSize: page.value.size, id: id.value}).then(res => {
    modelList.value = res.data.data?.records
    page.value.total = res.data.data?.total
    page.value.current = res.data.data?.current
    page.value.size = res.data.data?.size
  })
}

const id = ref()

const page = ref<PageParams>({current: 1, size: 20, total: 0})

const statusDialog = ref({
  trainDialog: false,
  lstmDialog: false,
  lstmAdd: null,
  predictDialog: false,
})

const dict = ref<Dict[]>()

const modelDict = ref<Dict[]>()

const lstm = ref<TrainModelLstm>()

const train = ref<TrainModel>({
  batchSize: 24,
  epoch: 50,
  featureStart: 0,
  featureStep: 1,
  featureStop: 0,
  labelStart: 0,
  labelStep: 1,
  labelStop: 0,
  lstm: [],
  name: "",
  skip: 0,
  standard: true,
  testRate: 0.8,
  timeWindow: true
})

const file = ref()

const trainRef = ref<FormInstance>()

const modelList = ref<LstmListDataRecords[]>()

const trainRules = ref<FormRules>({
  name: [
    {required: true, message: '请输入模型名称', trigger: 'blur'}
  ],
  batchSize: [
    {required: true, message: '请输入批次大小', trigger: 'blur'},
    {pattern: /^[1-9]\d*$/, message: '请输入大于0的整数', trigger: 'blur'}
  ],
  epoch: [
    {required: true, message: '请输入迭代次数', trigger: 'blur'},
    {pattern: /^[1-9]\d*$/, message: '请输入大于0的整数', trigger: 'blur'}
  ],
  featureStart: [
    {required: true, message: '请输入特征起始值', trigger: 'blur'},
    {pattern: /^(0|[1-9]\d*)$/, message: '请输入大于等于0的整数', trigger: 'blur'}
  ],
  featureStep: [
    {
      required: true, message: '请输入特征步长', trigger: 'blur',
    },
    {pattern: /^([1-9]\d*)$/, message: '请输入大于0的整数', trigger: 'blur'}
  ],
  featureStop: [
    {required: true, message: '请输入特征结束值', trigger: 'blur'},
    {pattern: /^([1-9]\d*)$/, message: '请输入大于0的整数', trigger: 'blur'}
  ],
  labelStart: [
    {required: true, message: '请输入标签起始值', trigger: 'blur'},
    {pattern: /^(0|[1-9]\d*)$/, message: '请输入大于等于0的整数', trigger: 'blur'}
  ],
  labelStep: [
    {
      required: true, message: '请输入标签步长', trigger: 'blur',
    },
    {
      pattern: /^([1-9]\d*)$/, message: '请输入大于0的整数', trigger: 'blur'
    }
  ],
  labelStop: [
    {required: true, message: '请输入标签结束值', trigger: 'blur'},
    {
      pattern: /^([1-9]\d*)$/, message: '请输入大于0的整数', trigger: 'blur'
    }
  ],
  lstm: [
    {required: true, message: '请增加层级', trigger: 'blur'},
  ],
  skip: [
    {required: true, message: '请输入跳过行数', trigger: 'blur'}
  ],
  standard: [
    {required: true, message: '请选择是否使用标准化', trigger: 'blur'}
  ],
  testRate: [
    {
      required: true, message: '请输入测试集比例', trigger: 'blur',
    }
  ],
  timeWindow: [
    {required: true, message: '请选择是否使用滑动窗口', trigger: 'blur'},
  ],
})

const lstmRef = ref<FormInstance>()

const lstmRules = ref<FormRules>({
  hiddenCell: [
    {required: true, message: '请输入隐藏神经单元数', trigger: 'blur'},
    {pattern: /^([1-9]\d*)$/, message: '请输入大于0的整数', trigger: 'blur'}
  ],
  l2: [
    {required: true, message: '请输入L2正则化系数', trigger: 'blur'}
  ],
  learningRate: [
    {required: true, message: '请输入学习率', trigger: 'blur'},
    {
      pattern: /^([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$/, message: '请输入大于0的浮点数', trigger: 'blur'
    }
  ],
  updater: [
    {required: true, message: '请输入更新器', trigger: 'blur'},
    {
      validator: (rule, value, callback) => {
        if (dict.value.findIndex((item) => item.code === value) == -1) {
          callback("请正确选择更新器")
        } else {
          callback()
        }
      }, trigger: 'blur'
    }
  ]
})

const predictList = ref<PredictData>({input: null, modelId: null})

const handleAddLayer = (index?: number) => {
  lstm.value = index != null ? Common.deepCopy(train.value.lstm[index]) : {
    hiddenCell: 7, l2: 0.001, learningRate: 0.001, updater: 0
  }
  statusDialog.value.lstmDialog = true
  statusDialog.value.lstmAdd = index
}

const handleAdd = () => {
  lstmRef.value.validate((valid) => {
    if (valid) {
      train.value.lstm.push(lstm.value)
      statusDialog.value.lstmDialog = false
    }
  })
}

const handleDeleteLSTM = (index: number) => {
  train.value.lstm.splice(index, 1)
}

const beforeUpload = (file_: File) => {
  file.value = file_
}

const trainModel = () => {
  trainRef.value.validate((valid) => {
    if (valid) {
      if (file.value == null) {
        ElMessage.error("请选择训练集文件")
        return
      }
      const formData = new FormData();
      formData.set('file', file.value);
      formData.set("post", new Blob([JSON.stringify(train.value)], {type: "application/json"}))

      // 发送POST请求
      trainLstm(formData).then(res => {
        ElMessage.success(res.data.msg)
        statusDialog.value.trainDialog = false
        file.value = null
        train.value = {
          batchSize: 24,
          epoch: 50,
          featureStart: 0,
          featureStep: 1,
          featureStop: 0,
          labelStart: 0,
          labelStep: 1,
          labelStop: 0,
          lstm: [],
          name: "",
          skip: 0,
          standard: true,
          testRate: 0.8,
          timeWindow: true
        }
      })
    }
  })
}

const handleEdit = () => {
  lstmRef.value.validate((valid) => {
    if (valid) {
      train.value.lstm[statusDialog.value.lstmAdd] = lstm.value
      statusDialog.value.lstmDialog = false
    }
  })
}

const handleHttpRequest = () => {

}

const handlePredict = (value: LstmListDataRecords) => {
  if (value.status != 3) {
    return
  }
  predictList.value.input = new Array(value.featureStep).fill(0).map(() => {
    return new Array(value.featureStop - value.featureStart).fill(0)
  })
  predictList.value.modelId = value.id
  statusDialog.value.predictDialog = true
}

const handlePredictPost = () => {
  predict(predictList.value).then(res => {
    let data: [][][] = res.data.data
    const transformedArray = [];

    for (let outerIndex = 0; outerIndex < data.length; outerIndex++) {
      for (let innerIndex = 0; innerIndex < data[outerIndex].length; innerIndex++) {
        const value = data[outerIndex][innerIndex]; // 获取实际的值
        transformedArray.push({
          value: [outerIndex, innerIndex, value] // 创建一个包含索引和值的数组
        });
      }
    }
    let option = {
      tooltip: {},
      visualMap: {
        max: transformedArray.reduce((a, b) => {
          return b.value[2] - a.value[2] > 0 ? b : a
        }, {value: [0, 1, 0]}).value[2],
        inRange: {
          color: [
            '#313695',
            '#4575b4',
            '#74add1',
            '#abd9e9',
            '#e0f3f8',
            '#ffffbf',
            '#fee090',
            '#fdae61',
            '#f46d43',
            '#d73027',
            '#a50026'
          ]
        }
      },
      xAxis3D: {
        type: 'category',
      },
      yAxis3D: {
        type: 'category',
      },
      zAxis3D: {
        type: 'value'
      },
      grid3D: {
        boxWidth: 200,
        boxDepth: 80,
        viewControl: {
          // projection: 'orthographic'
        },
        light: {
          main: {
            intensity: 1.2,
            shadow: true
          },
          ambient: {
            intensity: 0.3
          }
        }
      },
      series: [
        {
          type: 'bar3D',
          data: transformedArray,
          shading: 'lambert',
          label: {
            fontSize: 16,
            borderWidth: 1
          },
          emphasis: {
            label: {
              fontSize: 20,
              color: '#900'
            },
            itemStyle: {
              color: '#900'
            }
          }
        }
      ]
    };
    let dom = document.getElementById('echarts');
    (echarts.getInstanceByDom(dom) ?? echarts.init(dom)).setOption(option);
  })
}

const handleStep = () => {
  ElMessageBox.prompt('请输入预测步长', '多步预测', {
    confirmButtonText: '提交',
    cancelButtonText: '取消',
    inputPattern:
        /^[1-9]+[0-9]*$/,
    inputErrorMessage: '步长格式不正确',
  }).then(({value}) => {
    predictStep({...predictList.value, step: Number(value)}).then(res => {
    })
  })
}

const handleRemove = (row) => {
  const {id} = row
  deleteModel(id).then(res => {
    ElMessage.success(res.data.msg)
    initModel()
  })
}
</script>
<style scoped>
.el-form-item__content > .el-select {
  width: 170px
}

.el-form-item__content > .el-input {
  width: 170px;
}

.el-form-item__content > .el-date-editor {
  width: 170px !important;
}

.el-form-item__content > .el-switch {
  width: 170px;
}

.form .el-form-item__content > .el-button {
  width: 170px;
}

.el-form-item__content > .el-dropdown {
  width: 170px;
}

.lstm-param {
  width: 100%;
  margin: 0;
  align-items: center;
}

.el-form {
  text-align: start;
}

.form-file {
  width: 100%;
  margin: 0;
}

.upload-demo {
  width: 100%;
}

.predict-cell .el-form-item__content > .el-input > :deep(.el-input__wrapper) {
  border: 1px solid #1f4cb3 !important;
  margin: -1px 0 0 -1px !important;
  border-radius: 0 !important;
}

.predict-cell .el-form-item__content > .el-input {
  width: 50px;
}

.predict-item > :deep(.el-form-item__content) {
  justify-content: space-around;
}

.predict-cell {
  padding: 2px;
}
</style>
